package com.example.lt.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @Author 小涛Tao
 * @Date: 2024/07/27/18:49
 * version 1.0 注释：redis 工具类
 **/
@Service(value = "redisUtil")
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 添加有序集合
     *
     * @param key
     * @param score
     * @param val
     * @param time
     */
    public void zadd(String key, double score, Object val, long time) {
        redisTemplate.opsForZSet().add(key, val, score);

        this.expire(key, time);
    }

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取有序集合（Sorted Set）中的所有元素，并且是降序排序
     * @param key
     * @return
     */
    public Set zGet(String key){
        return redisTemplate.opsForZSet().reverseRange(key,0,-1);
    }

    /**
     * 获取有序集合中指定范围内的元素及其分数，先从高到低排好序再来获取
     * @param key
     * @param pageNum 获取第 pageNum 页
     * @param pageSize 获取 pageSize 个元素
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zSetGetByPage(String key, long pageNum, long pageSize) {
        try {
            if (redisTemplate.hasKey(key)) { // 检查 key 在 redis 中是否存在
                long start = (pageNum - 1) * pageSize; // 计算起始索引
                long end = pageNum * pageSize - 1; // 计算结束索引
                Long size = redisTemplate.opsForZSet().size(key); // 获取 key 对应的有序集合【ZSet】中的元素数量
                if (end > size) { // 根据有序集合中的元素总数来调整结束索引
                    end = -1;
                }

                // 从 Redis 的有序集合【ZSet】中按分数从高到低的顺序获取指定范围内的元素及其分数
                return redisTemplate.opsForZSet().reverseRangeWithScores(key,start,end);
            } else {
                return null; // 如果在 redis 中没有这个 key 则返回 null
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<Object, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取 hashKey 对应的所有键值
     * @param key 键
     * @return 对应的键值对
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 删除map中指定key的数据
     *
     * @param key 键
     * @param mapKey 对应多个键值，要删除的键值
     * @return
     */
    public boolean hdel(String key, List<String> mapKey) {
        try {
            redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                for (String fieldKey : mapKey) {
                    connection.hDel(redisTemplate.getStringSerializer().serialize(key), redisTemplate.getStringSerializer().serialize(fieldKey));
                }
                return null;
            });
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {

        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 随机获取一个视频id
     * @param key
     * @return
     */
    public Object sRandom(String key){
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 随机获取set中的元素
     * @param keys
     * @return
     */
    public List<Object> sRandom(List<String> keys) {
        final List<Object> list = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {

                for (String key : keys) {
                    connection.sRandMember(key.getBytes());
                }
                return null;
            }
        });
        final List result = new ArrayList<>();
        for (Object o : list) {
            if (o != null) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public Long incr(String key, long delta) {
        if (delta <= 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    // 接受一个管道
    public List pipeline(RedisCallback redisCallback){
        return redisTemplate.executePipelined(redisCallback);
    }

}
